import os
import threading
import time
import tkinter as tk
from datetime import datetime
from tkinter import ttk, scrolledtext, messagebox

import serial
import serial.tools.list_ports
from tinydb import TinyDB
from ttkbootstrap.constants import *

from app.util.annotation_util import annotate, ClassInfo
from app.view.base_view import View

# 确保配置目录存在
if not os.path.exists('config'):
    os.makedirs('config')

# 数据库初始化
db = TinyDB('config/serial_cmd.json')
commands_table = db.table('commands')
settings_table = db.table('settings')

# 默认命令
DEFAULT_COMMANDS = [
    {"name": "查询状态", "command": "AT+STATUS?", "add_newline": True},
    {"name": "重启设备", "command": "AT+RESET", "add_newline": True},
    {"name": "设置参数", "command": "AT+SET=param,value", "add_newline": True}
]


@annotate(ClassInfo(name="Serial CMD", category="dev_tool"))
class SerialCmdView(View):
    def __init__(self, parent, controller):
        super().__init__(parent, controller)
        # 串口相关变量
        self.ser = None
        self.is_connected = False
        self.receive_thread = None
        self.stop_event = threading.Event()

        # 过滤相关变量
        self.filter_text = tk.StringVar()

        # 命令列表
        self.command_frames = []

        # 创建界面
        self._create_widgets()

        # 加载保存的命令
        self.load_commands()

        # 加载保存的设置
        self.load_settings()

        # 绑定关闭事件
        # self.protocol("WM_DELETE_WINDOW", self.on_close)

    def _create_widgets(self):
        # 创建主框架
        main_frame = ttk.Frame(self, padding=10)
        main_frame.pack(fill=BOTH, expand=True)

        # 1. 串口设置区域
        serial_frame = ttk.LabelFrame(main_frame, text="串口设置", padding=10)
        serial_frame.pack(fill=X, pady=(0, 10))

        # 串口选择
        ttk.Label(serial_frame, text="端口:").grid(row=0, column=0, padx=5, pady=5, sticky=W)
        self.port_combo = ttk.Combobox(serial_frame, width=10)
        self.port_combo.grid(row=0, column=1, padx=5, pady=5)

        # 刷新端口按钮
        refresh_btn = ttk.Button(serial_frame, text="刷新", command=self.refresh_ports)
        refresh_btn.grid(row=0, column=2, padx=5, pady=5)

        # 波特率
        ttk.Label(serial_frame, text="波特率:").grid(row=0, column=3, padx=5, pady=5, sticky=W)
        self.baudrate_combo = ttk.Combobox(serial_frame, values=["9600", "19200", "38400", "57600", "115200", "230400"],
                                           width=10)
        self.baudrate_combo.current(4)  # 默认115200
        self.baudrate_combo.grid(row=0, column=4, padx=5, pady=5)

        # 数据位
        ttk.Label(serial_frame, text="数据位:").grid(row=0, column=5, padx=5, pady=5, sticky=W)
        self.databits_combo = ttk.Combobox(serial_frame, values=["8", "7", "6", "5"], width=5)
        self.databits_combo.current(0)  # 默认8
        self.databits_combo.grid(row=0, column=6, padx=5, pady=5)

        # 停止位
        ttk.Label(serial_frame, text="停止位:").grid(row=0, column=7, padx=5, pady=5, sticky=W)
        self.stopbits_combo = ttk.Combobox(serial_frame, values=["1", "1.5", "2"], width=5)
        self.stopbits_combo.current(0)  # 默认1
        self.stopbits_combo.grid(row=0, column=8, padx=5, pady=5)

        # 校验位
        ttk.Label(serial_frame, text="校验位:").grid(row=0, column=9, padx=5, pady=5, sticky=W)
        self.parity_combo = ttk.Combobox(serial_frame, values=["None", "Odd", "Even", "Mark", "Space"], width=7)
        self.parity_combo.current(0)  # 默认None
        self.parity_combo.grid(row=0, column=10, padx=5, pady=5)

        # 连接/断开按钮
        self.connect_btn = ttk.Button(serial_frame, text="打开串口", command=self.toggle_connection)
        self.connect_btn.grid(row=0, column=11, padx=10, pady=5)

        # 2. 日志显示区域
        log_frame = ttk.LabelFrame(main_frame, text="通信日志", padding=10)
        log_frame.pack(fill=BOTH, expand=True, pady=(0, 10))

        # 日志过滤
        filter_frame = ttk.Frame(log_frame)
        filter_frame.pack(fill=X, pady=(0, 5))

        ttk.Label(filter_frame, text="过滤:").pack(side=LEFT, padx=5)
        self.filter_entry = ttk.Entry(filter_frame, textvariable=self.filter_text, width=30)
        self.filter_entry.pack(side=LEFT, padx=5, fill=X, expand=True)

        clear_log_btn = ttk.Button(filter_frame, text="清除日志", command=self.clear_log)
        clear_log_btn.pack(side=RIGHT, padx=5)

        # 日志文本框
        self.log_text = scrolledtext.ScrolledText(log_frame, wrap=tk.WORD, state=DISABLED, height=10)
        self.log_text.pack(fill=BOTH, expand=True)

        # 设置日志标签颜色
        self.log_text.tag_config("receive", foreground="blue")
        self.log_text.tag_config("send", foreground="green")
        self.log_text.tag_config("system", foreground="black")

        # 3. 快速发送
        self.quick_send_entry = ttk.Entry(log_frame)
        self.quick_send_entry.pack(side=LEFT, fill=X, expand=True, padx=(0, 5), pady=(3, 3))
        self.quick_send_entry.bind('<Return>', lambda e: self.send_quick_command())

        self.quick_newline_var = tk.BooleanVar(value=True)
        newline_check = ttk.Checkbutton(log_frame, text="添加换行", variable=self.quick_newline_var)
        newline_check.pack(side=LEFT, padx=(0, 5))

        quick_send_btn = ttk.Button(log_frame, text="发送", command=self.send_quick_command, bootstyle=SUCCESS)
        quick_send_btn.pack(side=LEFT)

        # 4. 命令管理区域
        cmd_frame = ttk.LabelFrame(main_frame, text="命令管理", padding=5)
        cmd_frame.pack(fill=BOTH, expand=True)

        # 命令列表标题行
        header_frame = ttk.Frame(cmd_frame)
        header_frame.pack(fill=X, pady=(0, 5))

        ttk.Label(header_frame, text="选择", width=5).grid(row=0, column=0, padx=5)
        ttk.Label(header_frame, text="命令名称", width=15).grid(row=0, column=1, padx=5)
        ttk.Label(header_frame, text="命令内容", width=40).grid(row=0, column=2, padx=5)
        ttk.Label(header_frame, text="换行", width=5).grid(row=0, column=3, padx=5)
        ttk.Label(header_frame, text="操作", width=6).grid(row=0, column=4, padx=5)

        # 创建带滚动条的命令列表区域
        self.cmd_container_frame = ttk.Frame(cmd_frame)
        self.cmd_container_frame.pack(fill=BOTH, expand=True)

        # 画布和滚动条用于支持大量命令的滚动
        self.cmd_canvas = tk.Canvas(self.cmd_container_frame)
        self.cmd_scrollbar = ttk.Scrollbar(self.cmd_container_frame, orient="vertical", command=self.cmd_canvas.yview)
        self.cmd_scrollable_frame = ttk.Frame(self.cmd_canvas)

        self.cmd_scrollable_frame.bind(
            "<Configure>",
            lambda e: self.cmd_canvas.configure(scrollregion=self.cmd_canvas.bbox("all"))
        )

        self.cmd_canvas.create_window((0, 0), window=self.cmd_scrollable_frame, anchor="nw")
        self.cmd_canvas.configure(yscrollcommand=self.cmd_scrollbar.set)

        self.cmd_canvas.pack(side="left", fill="both", expand=True)
        self.cmd_scrollbar.pack(side="right", fill="y")

        # 命令操作按钮
        btn_frame = ttk.Frame(cmd_frame)
        btn_frame.pack(fill=X, pady=10)

        add_btn = ttk.Button(btn_frame, text="添加命令", command=self.add_command)
        add_btn.pack(side=LEFT, padx=2, fill=X, expand=True)

        delete_selected_btn = ttk.Button(btn_frame, text="删除选中命令", command=self.delete_selected_commands)
        delete_selected_btn.pack(side=LEFT, padx=2, fill=X, expand=True)

        send_selected_btn = ttk.Button(btn_frame, text="发送选中命令", command=self.send_selected_commands,
                                       bootstyle=SUCCESS)
        send_selected_btn.pack(side=LEFT, padx=2, fill=X, expand=True)

        select_all_btn = ttk.Button(btn_frame, text="全选/取消全选", command=self.toggle_select_all)
        select_all_btn.pack(side=LEFT, padx=2, fill=X, expand=True)

        reset_btn = ttk.Button(btn_frame, text="恢复默认", command=self.reset_to_default_commands)
        reset_btn.pack(side=LEFT, padx=2, fill=X, expand=True)

        # 初始刷新端口列表
        self.refresh_ports()

    def refresh_ports(self):
        """刷新串口列表"""
        self.port_combo['values'] = []
        ports = serial.tools.list_ports.comports()
        port_list = [port.device for port in ports]
        self.port_combo['values'] = port_list
        if port_list:
            self.port_combo.current(0)

    def toggle_connection(self):
        """切换串口连接状态"""
        if self.is_connected:
            self.close_serial()
        else:
            self.open_serial()

    def open_serial(self):
        """打开串口"""
        try:
            port = self.port_combo.get()
            if not port:
                messagebox.showerror("错误", "请选择串口")
                return

            baudrate = int(self.baudrate_combo.get())
            databits = int(self.databits_combo.get())

            # 转换停止位
            stopbits_text = self.stopbits_combo.get()
            if stopbits_text == "1":
                stopbits = serial.STOPBITS_ONE
            elif stopbits_text == "1.5":
                stopbits = serial.STOPBITS_ONE_POINT_FIVE
            else:  # 2
                stopbits = serial.STOPBITS_TWO

            # 转换校验位
            parity_text = self.parity_combo.get()
            if parity_text == "None":
                parity = serial.PARITY_NONE
            elif parity_text == "Odd":
                parity = serial.PARITY_ODD
            elif parity_text == "Even":
                parity = serial.PARITY_EVEN
            elif parity_text == "Mark":
                parity = serial.PARITY_MARK
            else:  # Space
                parity = serial.PARITY_SPACE

            # 打开串口
            self.ser = serial.Serial(
                port=port,
                baudrate=baudrate,
                bytesize=databits,
                stopbits=stopbits,
                parity=parity,
                timeout=0.1
            )

            if self.ser.is_open:
                self.is_connected = True
                self.connect_btn.config(text="关闭串口", bootstyle=DANGER)
                self.log("串口已打开: " + port)

                # 保存当前设置
                self.save_settings()

                # 启动接收线程
                self.stop_event.clear()
                self.receive_thread = threading.Thread(target=self.receive_data, daemon=True)
                self.receive_thread.start()
            else:
                self.log("无法打开串口: " + port)

        except Exception as e:
            self.log(f"打开串口失败: {str(e)}")
            messagebox.showerror("错误", f"打开串口失败: {str(e)}")

    def close_serial(self):
        """关闭串口"""
        if self.is_connected and self.ser:
            self.stop_event.set()
            if self.receive_thread and self.receive_thread.is_alive():
                self.receive_thread.join()
            self.ser.close()
            self.is_connected = False
            self.connect_btn.config(text="打开串口", bootstyle=DEFAULT)
            self.log("串口已关闭")

    def receive_data(self):
        """接收串口数据的线程函数"""
        while not self.stop_event.is_set():
            try:
                if self.ser and self.ser.in_waiting > 0:
                    data = self.ser.read(self.ser.in_waiting)
                    try:
                        # 尝试以UTF-8解码
                        text = data.decode('utf-8')
                    except UnicodeDecodeError:
                        # 解码失败，使用十六进制显示
                        text = f"[二进制数据] {data.hex(' ')}"

                    # 应用过滤
                    filter_str = self.filter_text.get().lower()
                    if not filter_str or filter_str in text.lower():
                        self.log(f"{text}", "receive")
                time.sleep(0.01)
            except Exception as e:
                self.log(f"接收数据错误: {str(e)}")
                break

    def send_quick_command(self):
        """发送快速输入框中的命令"""
        if not self.is_connected:
            messagebox.showwarning("警告", "请先打开串口")
            return

        cmd_content = self.quick_send_entry.get().strip()
        if not cmd_content:
            return

        try:
            # 如果需要，添加换行符
            final_cmd = cmd_content + ('\n' if self.quick_newline_var.get() else '')

            # 发送数据
            self.ser.write(final_cmd.encode('utf-8'))
            self.log(f"{final_cmd}", "send")

            # 清空输入框
            self.quick_send_entry.delete(0, END)

        except Exception as e:
            self.log(f"发送命令失败: {str(e)}")
            messagebox.showerror("错误", f"发送命令失败: {str(e)}")

    def send_selected_commands(self):
        """发送选中的命令"""
        if not self.is_connected:
            messagebox.showwarning("警告", "请先打开串口")
            return

        selected_commands = [frame.get_data() for frame in self.command_frames if frame.get_data()["selected"]]
        if not selected_commands:
            messagebox.showwarning("警告", "请先选择要发送的命令")
            return

        self.log(f"开始发送 {len(selected_commands)} 条选中命令", "system")

        # 按顺序发送命令
        for cmd in selected_commands:
            if cmd["command"]:  # 只发送有内容的命令
                try:
                    # 如果需要，添加换行符
                    final_cmd = cmd["command"] + ('\n' if cmd["add_newline"] else '')

                    # 发送数据
                    self.ser.write(final_cmd.encode('utf-8'))
                    self.log(f"{final_cmd}", "send")

                    # 每条命令之间等待100ms
                    time.sleep(0.1)
                except Exception as e:
                    self.log(f"发送命令失败: {str(e)}")
                    messagebox.showerror("错误", f"发送命令失败: {str(e)}")
                    break

        self.log("选中命令发送完成", "system")

    def log(self, message, log_type="system"):
        """在日志区域显示消息，带时间戳"""
        timestamp = datetime.now().strftime("%H:%M:%S.%f")[:-3]  # 时分秒毫秒

        # 根据日志类型设置前缀和标签
        if log_type == "receive":
            prefix = f"[{timestamp}] [接收] "
            tag = "receive"
        elif log_type == "send":
            prefix = f"[{timestamp}] [发送] "
            tag = "send"
        else:  # system
            prefix = f"[{timestamp}] [系统] "
            tag = "system"

        # 在UI线程中更新日志
        def update_log():
            self.log_text.config(state=NORMAL)
            self.log_text.insert(END, prefix + message + "\n")

            # 设置标签颜色
            self.log_text.tag_add(tag, "end-2l linestart", "end-1l lineend")

            # 自动滚动到底部
            self.log_text.see(END)
            self.log_text.config(state=DISABLED)

        # 确保在主线程中执行
        self.after(0, update_log)

    def clear_log(self):
        """清除日志内容"""
        self.log_text.config(state=NORMAL)
        self.log_text.delete(1.0, END)
        self.log_text.config(state=DISABLED)
        self.log("日志已清除")

    def load_commands(self):
        """从数据库加载命令"""
        # 清除现有命令
        self.clear_all_commands()

        commands = commands_table.all()
        if not commands:  # 如果没有保存的命令，使用默认命令
            commands = DEFAULT_COMMANDS
            commands_table.insert_multiple(DEFAULT_COMMANDS)

        # 添加命令
        for i, cmd in enumerate(commands):
            self.add_command_frame(cmd, i)

    def add_command_frame(self, command_data, index):
        """添加一个命令框架"""
        cmd_frame = CommandFrame(
            self.cmd_scrollable_frame,
            command_data,
            self.delete_command,
            index
        )
        cmd_frame.pack(fill=X, pady=2)
        self.command_frames.insert(index, cmd_frame)

        # 更新所有命令的索引
        self.update_command_indices()

        return cmd_frame

    def update_command_indices(self):
        """更新所有命令的索引"""
        for i, frame in enumerate(self.command_frames):
            frame.set_index(i)

    def clear_all_commands(self):
        """清除所有命令"""
        for frame in self.command_frames:
            frame.destroy()
        self.command_frames = []

    def save_commands(self):
        """保存命令到数据库"""
        commands_table.truncate()  # 清空现有数据

        commands = []
        for frame in self.command_frames:
            cmd_data = frame.get_data()
            # 只保存有命令内容的条目
            if cmd_data["command"]:
                commands.append({
                    "name": cmd_data["name"],
                    "command": cmd_data["command"],
                    "add_newline": cmd_data["add_newline"]
                })

        if commands:
            commands_table.insert_multiple(commands)

    def load_settings(self):
        """加载保存的设置"""
        settings = settings_table.all()
        if settings:
            last_settings = settings[-1]  # 获取最后一次保存的设置
            if "baudrate" in last_settings:
                self.baudrate_combo.set(str(last_settings["baudrate"]))
            if "databits" in last_settings:
                self.databits_combo.set(str(last_settings["databits"]))
            if "stopbits" in last_settings:
                self.stopbits_combo.set(last_settings["stopbits"])
            if "parity" in last_settings:
                self.parity_combo.set(last_settings["parity"])

    def save_settings(self):
        """保存当前设置"""
        settings = {
            "baudrate": int(self.baudrate_combo.get()),
            "databits": int(self.databits_combo.get()),
            "stopbits": self.stopbits_combo.get(),
            "parity": self.parity_combo.get()
        }
        settings_table.truncate()  # 只保留最新的设置
        settings_table.insert(settings)

    def add_command(self):
        """添加新命令"""
        # 创建新命令数据
        new_cmd = {
            "name": f"新命令{len(self.command_frames) + 1}",
            "command": "",
            "add_newline": True
        }

        # 添加到UI
        self.add_command_frame(new_cmd, len(self.command_frames))

        # 保存命令
        self.save_commands()

        # 滚动到最后
        self.cmd_canvas.after(100, lambda: self.cmd_canvas.yview_moveto(1.0))

    def delete_command(self, index):
        """删除指定索引的命令"""
        if 0 <= index < len(self.command_frames):
            # 销毁UI组件
            self.command_frames[index].destroy()
            # 从列表中移除
            del self.command_frames[index]
            # 更新索引
            self.update_command_indices()
            # 保存更改
            self.save_commands()

    def delete_selected_commands(self):
        """删除所有选中的命令"""
        # 收集所有选中的命令索引（倒序处理，避免索引变化问题）
        selected_indices = [i for i, frame in enumerate(self.command_frames) if frame.get_data()["selected"]]
        selected_indices.sort(reverse=True)

        if not selected_indices:
            messagebox.showwarning("警告", "请先选择要删除的命令")
            return

        if messagebox.askyesno("确认", f"确定要删除选中的 {len(selected_indices)} 条命令吗？"):
            # 按倒序删除
            for i in selected_indices:
                self.delete_command(i)

    def toggle_select_all(self):
        """全选或取消全选"""
        if not self.command_frames:
            return

        # 检查当前是否全选
        all_selected = all(frame.get_data()["selected"] for frame in self.command_frames)

        # 设置相反的状态
        for frame in self.command_frames:
            frame.selected_var.set(not all_selected)

    def reset_to_default_commands(self):
        """恢复默认命令"""
        if messagebox.askyesno("确认", "确定要恢复默认命令吗？当前命令将被覆盖。"):
            commands_table.truncate()
            commands_table.insert_multiple(DEFAULT_COMMANDS)
            self.load_commands()

    def on_close(self):
        """关闭窗口时的处理"""
        # 保存当前命令
        self.save_commands()
        self.close_serial()
        self.destroy()

    def create_widgets(self):
        pass


class CommandFrame(ttk.Frame):
    """单个命令的UI组件"""

    def __init__(self, parent, command_data, delete_callback, index):
        super().__init__(parent, padding=5, relief=tk.RAISED, borderwidth=1)
        self.parent = parent
        self.delete_callback = delete_callback
        self.index = index

        # 命令数据
        self.name_var = tk.StringVar(value=command_data["name"])
        self.command_var = tk.StringVar(value=command_data["command"])
        self.newline_var = tk.BooleanVar(value=command_data["add_newline"])
        self.selected_var = tk.BooleanVar(value=False)

        # 绑定变量变化事件，自动保存
        self.name_var.trace_add("write", self.on_change)
        self.command_var.trace_add("write", self.on_change)
        self.newline_var.trace_add("write", self.on_change)

        # 创建UI元素
        self._create_widgets()

        # 网格布局权重设置
        self.grid_columnconfigure(1, weight=1)
        self.grid_columnconfigure(2, weight=3)

    def _create_widgets(self):
        # 选择复选框
        ttk.Checkbutton(self, variable=self.selected_var, width=5).grid(row=0, column=0, padx=5, pady=2, sticky=W)

        # 命令名称
        ttk.Entry(self, textvariable=self.name_var, width=40).grid(row=0, column=1, padx=5, pady=2, sticky=EW)

        # 命令内容
        ttk.Entry(self, textvariable=self.command_var, width=80).grid(row=0, column=2, padx=5, pady=2, sticky=EW)

        # 换行控制
        ttk.Checkbutton(self, text="换行", variable=self.newline_var, width=5).grid(row=0, column=3, padx=5, pady=2,
                                                                                    sticky=W)

        # 删除按钮
        ttk.Button(self, text="删除", command=self.on_delete, bootstyle=DANGER, width=6).grid(row=0, column=4, padx=5,
                                                                                              pady=2)

    def on_delete(self):
        """删除当前命令"""
        self.delete_callback(self.index)

    def on_change(self, *args):
        """当命令内容变化时触发"""
        # 通知父组件保存命令
        self.parent.save_commands()

    def get_data(self):
        """获取当前命令的数据"""
        return {
            "name": self.name_var.get().strip(),
            "command": self.command_var.get().strip(),
            "add_newline": self.newline_var.get(),
            "selected": self.selected_var.get()
        }

    def set_index(self, index):
        """更新索引"""
        self.index = index
